﻿namespace WebPacker.Tool
{
	using System;
	using System.Collections.Generic;
	using System.ComponentModel;

	internal sealed class CommandLine
	{
		private readonly List<Argument> arguments;

		public CommandLine()
		{
			this.arguments = new List<Argument>();

			Prefixes = new string[] { "-", "/", "--" };
			Separators = new string[] { "=" };
			IgnoreCase = true;
		}

		public CommandLine(string[] args)
			: this()
		{
			if (args == null)
			{
				throw new ArgumentNullException("args");
			}

			this.Parse(args);
		}

		public bool IgnoreCase { get; set; }

		public string[] Prefixes { get; set; }

		public string[] Separators { get; set; }

		public int Count
		{
			get { return arguments.Count; }
		}

		public string this[int position]
		{
			get { return GetPositional(position); }
		}

		public string this[string name]
		{
			get { return GetValue(name); }
		}

		public bool Contains(string name)
		{
			return GetArgument(name) != null;
		}

		public bool Contains(int position)
		{
			return GetArgument(position) != null;
		}

		public string GetValue(string name)
		{
			var argument = GetArgument(name);
			return argument != null ? argument.Value : null;
		}

		public T GetValue<T>(string name)
		{
			return GetValue<T>(name, default(T));
		}

		public T GetValue<T>(string name, T defaultValue)
		{
			string value = GetValue(name);

			if (value != null)
			{
				TypeConverter converter = TypeDescriptor.GetConverter(typeof(T));
				return (T)converter.ConvertFromString(value);
			}

			return defaultValue;
		}

		public IEnumerable<string> GetCollection()
		{
			return GetCollection(0);
		}

		public IEnumerable<string> GetCollection(int startPosition)
		{
			foreach (var arg in arguments)
			{
				if (arg.Position >= startPosition)
				{
					yield return arg.Value;
				}
			}
		}

		public string GetPositional(int position)
		{
			var argument = GetArgument(position);
			return argument != null ? argument.Value : null;
		}

		private Argument GetArgument(int position)
		{
			if (position < 0)
			{
				throw new ArgumentOutOfRangeException("position");
			}

			return arguments.Find(x => x.Position == position);
		}

		private Argument GetArgument(string name)
		{
			if (name == null)
			{
				throw new ArgumentNullException("name");
			}

			return arguments.Find(x => 0 == string.Compare(x.Name, name, IgnoreCase));
		}

		private void Parse(string[] args)
		{
			var position = 0;

			foreach (var arg in args)
			{
				var name = arg;
				string value = null;
				var positional = true;

				foreach (var prefix in Prefixes)
				{
					if (name.StartsWith(prefix))
					{
						name = name.Substring(prefix.Length);
						positional = false;
						break;
					}
				}

				if (!positional)
				{
					foreach (var separator in Separators)
					{
						var index = name.IndexOf(separator);

						if (index != -1)
						{
							if (index < name.Length)
							{
								value = name.Substring(index + separator.Length);
								name = name.Substring(0, index);
							}

							break;
						}
					}

					arguments.Add(new Argument(name, value));
				}
				else
				{
					arguments.Add(new Argument(position++, name));
				}
			}
		}

		private class Argument
		{
			private readonly string name;
			private readonly string value;
			private readonly int position;

			public Argument(string name, string value)
			{
				this.name = name;
				this.value = value;
				this.position = -1;
			}

			public Argument(int position, string value)
			{
				this.position = position;
				this.value = value;
			}

			public string Name
			{
				get { return name; }
			}

			public string Value
			{
				get { return value; }
			}

			public int Position
			{
				get { return position; }
			}
		}
	}
}